package de.joergviola.mobj.generator;

import java.io.PrintWriter;

import javax.persistence.Id;
import javax.persistence.Transient;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class EntityGenerator extends Generator {

	private String packageName;
	private String className;
	private JClassType persistable;

	interface FieldVisitor {
		public void seperator();

		public void field(JField field);
	}

	@Override
	public String generate(TreeLogger logger, GeneratorContext context,
			String typeName) throws UnableToCompleteException {
		TypeOracle typeOracle = context.getTypeOracle();
		try {
			// get classType and save instance variables
			JClassType classType = typeOracle.getType(typeName);
			JClassType[] implementedInterfaces = classType
					.getImplementedInterfaces();
			JClassType entityInterface = implementedInterfaces[0];
			JParameterizedType parameterized = entityInterface
					.isParameterized();
			if (parameterized == null)
				throw new UnableToCompleteException();
			persistable = parameterized.getTypeArgs()[0];
			packageName = classType.getPackage().getName();
			className = classType.getSimpleSourceName() + "Impl";
			// Generate class source code
			generateClass(logger, context);
		} catch (Exception e) {
			// record to logger that Map generation threw an exception
			logger.log(TreeLogger.ERROR, "PropertyMap ERROR!!!", e);
		}
		// return the fully qualifed name of the class generated
		return packageName + "." + className;
	}

	private void generateClass(TreeLogger logger, GeneratorContext context) {
		// get print writer that receives the source code
		PrintWriter printWriter = context.tryCreate(logger, packageName,
				className);
		// print writer if null, source code has ALREADY been generated, return
		if (printWriter == null)
			return;
		// init composer, set class properties, create source writer
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
				packageName, className);
		composer.addImport("de.joergviola.mobj.shared.Entity");
		composer.addImport("de.joergviola.mobj.shared.EntityReader");
		composer.addImport("de.joergviola.mobj.shared.EntityWriter");
		composer.addImport("de.joergviola.mobj.shared.ObjectNotFoundException");
		composer.addImport("de.joergviola.mobj.shared.UnknownEntityException");
		composer.addImport("com.google.gwt.user.client.rpc.AsyncCallback");
		composer.addImplementedInterface("Entity<"
				+ persistable.getSimpleSourceName() + ">");
		SourceWriter sourceWriter = composer.createSourceWriter(context,
				printWriter);
		sourceWriter.indent();

		generateGetEntityName(sourceWriter);
		generateGetFieldNames(sourceWriter);
		generateGetValues(sourceWriter);
		generateGetObject(sourceWriter);
		generateGetId(sourceWriter);

		sourceWriter.outdent();
		sourceWriter.commit(logger);
	}

	private void generateGetValues(final SourceWriter writer) {
		writer.println("@Override");
		final String var = persistable.getSimpleSourceName().toLowerCase();
		writer.println("public void getValues("
				+ persistable.getSimpleSourceName() + " " + var
				+ ", EntityWriter writer) throws UnknownEntityException {");
		visitFields(new FieldVisitor() {

			@Override
			public void seperator() {
			}

			@Override
			public void field(JField field) {
				String type = getTypeName(field);
				writer.print("writer.write");
				writer.print(type);
				writer.print("(\"");
				writer.print(field.getName());
				writer.print("\", ");
				writer.print(var);
				writer.print(".");
				writer.print(getter(field));
				writer.println(");");
			}

		});
		writer.println("}");
	}

	private String getTypeName(JField field) {
		JType type = field.getType();
		String result = type.getSimpleSourceName();
		JParameterizedType parameterized = type.isParameterized();
		if (parameterized != null) {
			for (JClassType p : parameterized.getTypeArgs()) {
				result += p.getSimpleSourceName();
			}
		}
		if (type.isArray() != null) {
			result = result.substring(0, result.length() - 2) + "Array";
		}
		JClassType ct = type.isClass();
		if (ct != null) {
			for (JClassType interf : ct.getImplementedInterfaces()) {
				if (interf.getSimpleSourceName().equals("Persistable"))
					return "Persistable";
			}
		}
		return result;
	}

	private String getter(JField field) {
		String name = field.getName();
		String prefix = name.substring(0, 1).toUpperCase();
		return "get" + prefix + name.substring(1) + "()";
	}

	private String setter(JField field, String value) {
		String name = field.getName();
		String prefix = name.substring(0, 1).toUpperCase();
		return "set" + prefix + name.substring(1) + "(" + value + ")";
	}

	private void generateGetObject(final SourceWriter writer) {
		writer.println("@Override");
		final String var = persistable.getSimpleSourceName().toLowerCase();
		writer.println("public "
				+ persistable.getSimpleSourceName()
				+ " getObject(EntityReader reader) throws ObjectNotFoundException,UnknownEntityException {");
		writer.indent();
		writer.print("final ");
		writer.print(persistable.getSimpleSourceName());
		writer.print(" ");
		writer.print(var);
		writer.print(" = new ");
		writer.print(persistable.getSimpleSourceName());
		writer.println("();");
		visitFields(new FieldVisitor() {

			@Override
			public void seperator() {
			}

			@Override
			public void field(JField field) {
				String type = getTypeName(field);
				if ("Persistable".equals(type)) {

					String refType = field.getType().getQualifiedSourceName();
					writer.print("reader.readPersistable(\"" + field.getName()
							+ "\", " + refType + ".class" + ", "
							+ setterCallback(field, var) + ")");
				} else {
					writer.print(var);
					writer.print(".");
					String value = "reader.read" + type + "(\""
							+ field.getName() + "\")";
					writer.print(setter(field, value));
				}
				writer.println(";");
			}
		});
		writer.println("return " + var + ";");
		writer.outdent();
		writer.println("}");
	}

	protected String setterCallback(JField field, String var) {
		StringBuilder b = new StringBuilder();
		b.append("new AsyncCallback<");
		b.append(field.getType().getQualifiedSourceName());
		b.append(">(){");
		b.append("@Override public void onFailure(Throwable caught) {}");
		b.append("@Override public void onSuccess(");
		b.append(field.getType().getQualifiedSourceName());
		b.append(" value) {");
		b.append(var);
		b.append(".");
		b.append(setter(field, "value"));
		b.append(";}}");
		return b.toString();
	}

	private JField getIdField() {
		for (JField field : persistable.getFields()) {
			if (isTransient(field))
				continue;
			if (field.isAnnotationPresent(Id.class)) {
				return field;
			}
		}
		return null;
	}

	private void generateGetId(SourceWriter writer) {
		writer.println("@Override");
		String var = persistable.getSimpleSourceName().toLowerCase();
		writer.println("public Object getId("
				+ persistable.getSimpleSourceName() + " " + var + ") {");
		JField idField = getIdField();
		writer.print("    return ");
		if (idField != null) {
			writer.print(var);
			writer.print(".");
			writer.print(getter(idField));
		} else {
			writer.print("null");
		}
		writer.println(";");
		writer.println("}");
	}

	private void generateGetEntityName(SourceWriter writer) {
		writer.println("@Override");
		writer.println("public String getEntityName() {");
		String entityName = persistable.getSimpleSourceName().toUpperCase();
		Entity annotation = persistable.getAnnotation(Entity.class);
		if (annotation != null)
			entityName = annotation.name();
		writer.println("	return \"" + entityName + "\";");
		writer.println("}");
	}

	private void generateGetFieldNames(final SourceWriter writer) {
		writer.println("@Override");
		writer.println("public String[] getFieldnames() {");
		writer.print("    return new String[] {");
		visitFields(new FieldVisitor() {

			@Override
			public void seperator() {
				writer.print(",");
			}

			@Override
			public void field(JField field) {
				writer.print("\"");
				writer.print(field.getName());
				writer.print("\"");
			}
		});
		writer.println("};");
		writer.println("}");
	}

	private void visitFields(FieldVisitor fieldVisitor) {
		boolean sep = false;
		for (JField field : persistable.getFields()) {
			if (isTransient(field))
				continue;
			if (sep)
				fieldVisitor.seperator();
			fieldVisitor.field(field);
			sep = true;
		}
	}

	private boolean isTransient(JField field) {
		return field.isTransient() || field.isStatic()
				|| field.isAnnotationPresent(Transient.class);
	}

}
